bech32.js ➔ convert5to8   A
last analyzed

Complexity

Conditions 4

Size

Total Lines 18
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 14
dl 0
loc 18
ccs 13
cts 13
cp 1
crap 4
rs 9.7
c 0
b 0
f 0
1
/**
2
 * @license
3
 * Copyright (c) 2020 UMI
4
 *
5
 * Permission is hereby granted, free of charge, to any person obtaining a copy
6
 * of this software and associated documentation files (the "Software"), to deal
7
 * in the Software without restriction, including without limitation the rights
8
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
 * copies of the Software, and to permit persons to whom the Software is
10
 * furnished to do so, subject to the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be included in all
13
 * copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
 * SOFTWARE.
22
 */
23
24
'use strict'
25
26 1
const array = require('./array.js')
27 1
const converter = require('./converter.js')
28
29 1
const bech32Alphabet = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'
30
/**
31
 * @param {number[]} bytes
32
 * @returns {string}
33
 * @private
34
 */
35
function bech32Encode (bytes) {
36 7
  const prefix = converter.versionToPrefix((bytes[0] << 8) + bytes[1])
37 7
  const data = convert8to5(bytes.slice(2))
38 7
  const checksum = createChecksum(prefix, data)
39 7
  return prefix + '1' + data + checksum
40
}
41
/**
42
 * @param {string} bech32
43
 * @returns {number[]}
44
 * @private
45
 */
46
function bech32Decode (bech32) {
47 15
  if (bech32.length !== 62 && bech32.length !== 66) {
48 2
    throw new Error('bech32: invalid length')
49
  }
50 13
  const str = bech32.toLowerCase()
51 12
  const sepPos = str.lastIndexOf('1')
52 12
  if (sepPos === -1) {
53 1
    throw new Error('bech32: missing separator')
54
  }
55 11
  const pfx = str.slice(0, sepPos)
56 11
  const ver = converter.prefixToVersion(pfx)
57 8
  const data = str.slice(sepPos + 1)
58 8
  checkAlphabet(data)
59 7
  verifyChecksum(pfx, data)
60 6
  return array.arrayConcat(converter.uint16ToBytes(ver), convert5to8(data.slice(0, -6)))
61
}
62
/**
63
 * @param {string} data
64
 * @returns {number[]}
65
 * @private
66
 */
67
function convert5to8 (data) {
68 6
  let value = 0
69 6
  let bits = 0
70 6
  const bytes = strToBytes(data)
71 6
  const result = []
72 6
  for (let i = 0; i < bytes.length; i++) {
73 312
    value = (value << 5) | bytes[i]
74 312
    bits += 5
75 312
    while (bits >= 8) {
76 192
      bits -= 8
77 192
      result[result.length] = (value >> bits) & 0xff
78
    }
79
  }
80 6
  if ((bits >= 5) || (value << (8 - bits)) & 0xff) {
0 ignored issues
show
introduced by
You have used a bitwise operator & in a condition. Did you maybe want to use the logical operator &&
Loading history...
81 1
    throw new Error('bech32: invalid data')
82
  }
83 5
  return result
84
}
85
/**
86
 * @param {number[]} data
87
 * @returns {string}
88
 * @private
89
 */
90
function convert8to5 (data) {
91 7
  let value = 0
92 7
  let bits = 0
93 7
  let result = ''
94 7
  for (let i = 0; i < data.length; i++) {
95 224
    value = (value << 8) | data[i]
96 224
    bits += 8
97 224
    while (bits >= 5) {
98 357
      bits -= 5
99 357
      result += bech32Alphabet.charAt((value >> bits) & 0x1f)
100
    }
101
  }
102
  /* istanbul ignore else */
103 7
  if (bits > 0) {
104 7
    result += bech32Alphabet.charAt((value << (5 - bits)) & 0x1f)
105
  }
106 7
  return result
107
}
108
/**
109
 * @param {string} prefix
110
 * @param {string} data
111
 * @returns {string}
112
 * @private
113
 */
114
function createChecksum (prefix, data) {
115 7
  const bytes = strToBytes(data)
116 7
  const pfx = prefixExpand(prefix)
117 7
  const values = array.arrayNew(pfx.length + bytes.length + 6)
118 7
  array.arraySet(values, pfx)
119 7
  array.arraySet(values, bytes, pfx.length)
120 7
  const poly = polyMod(values) ^ 1
121 7
  let checksum = ''
122 7
  for (let i = 0; i < 6; i++) {
123 42
    checksum += bech32Alphabet.charAt((poly >> 5 * (5 - i)) & 31)
124
  }
125 7
  return checksum
126
}
127
/**
128
 * @param {number[]} values
129
 * @returns {number}
130
 * @private
131
 */
132
function polyMod (values) {
133 14
  const gen = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
134 14
  let chk = 1
135
  let top
136 14
  for (let i = 0, l = values.length; i < l; i++) {
137 942
    top = chk >> 25
138 942
    chk = (chk & 0x1ffffff) << 5 ^ values[i]
139 942
    for (let j = 0; j < 5; j++) {
140 4710
      chk ^= ((top >> j) & 1)
0 ignored issues
show
introduced by
You have used a bitwise operator & in a condition. Did you maybe want to use the logical operator &&
Loading history...
141
        ? gen[j]
142
        : 0
143
    }
144
  }
145 14
  return chk
146
}
147
/**
148
 * @param {string} prefix
149
 * @returns {number[]}
150
 * @private
151
 */
152
function prefixExpand (prefix) {
153 14
  const res = array.arrayNew((prefix.length * 2) + 1)
154 14
  for (let i = 0, l = prefix.length; i < l; i++) {
155 58
    const ord = prefix.charCodeAt(i)
156 58
    res[i] = ord >> 5
157 58
    res[i + l + 1] = ord & 31
158
  }
159 14
  return res
160
}
161
/**
162
 * @param {string} str
163
 * @returns {number[]}
164
 * @private
165
 */
166
function strToBytes (str) {
167 20
  const bytes = []
168 20
  for (let i = 0, l = str.length; i < l; i++) {
169 1082
    bytes[bytes.length] = bech32Alphabet.indexOf(str.charAt(i))
170
  }
171 20
  return bytes
172
}
173
/**
174
 * @param {string} prefix
175
 * @param {string} data
176
 * @private
177
 */
178
function verifyChecksum (prefix, data) {
179 7
  const pfx = prefixExpand(prefix)
180 7
  const bytes = strToBytes(data)
181 7
  const values = array.arrayNew(pfx.length + bytes.length)
182 7
  array.arraySet(values, pfx)
183 7
  array.arraySet(values, bytes, pfx.length)
184 7
  const poly = polyMod(values)
185 7
  if (poly !== 1) {
186 1
    throw new Error('bech32: invalid checksum')
187
  }
188
}
189
/**
190
 * @param {string} chars
191
 * @private
192
 */
193
function checkAlphabet (chars) {
194 8
  for (let i = 0, l = chars.length; i < l; i++) {
195 407
    if (bech32Alphabet.indexOf(chars.charAt(i)) === -1) {
196 1
      throw new Error('bech32: invalid character')
197
    }
198
  }
199
}
200
201 1
exports.bech32Decode = bech32Decode
202
exports.bech32Encode = bech32Encode
203